﻿//  Copyright (c) 2009 Ray Liang (http://www.dotnetage.com)
//  Dual licensed under the MIT and GPL licenses:
//  http://www.opensource.org/licenses/mit-license.php
//  http://www.gnu.org/licenses/gpl.html
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Reflection;
using System.Web.Script.Serialization;
using System.Web.UI.WebControls;

namespace DNA.Mvc.jQuery
{
    /// <summary>
    /// Provides the methods to build the jQuery option object.
    /// </summary>
    public class OptionBuilder
    {
        private Dictionary<string, string> opts = new Dictionary<string, string>();

        public Dictionary<string, string> Options
        {
            get { return opts; }
        }

        /// <summary>
        /// Add options by specified the options object.
        /// </summary>
        /// <param name="options">The options instance.</param>
        /// <returns>The current OptionBuilder instance.</returns>
        public OptionBuilder AddOptions(object options)
        {
            var properties = options.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (PropertyInfo option in properties)
            {
                jQueryOptionAttribute clientProperty = (jQueryOptionAttribute)(Attribute.GetCustomAttribute(option, typeof(jQueryOptionAttribute), true));
                jQueryIgnoreAttribute ignoreAttr = (jQueryIgnoreAttribute)(Attribute.GetCustomAttribute(option, typeof(jQueryIgnoreAttribute), true));
                if (ignoreAttr != null)
                    continue;
                object _value = null;
                string optName = option.Name;

                if (option.CanRead)
                    _value = option.GetValue(options, null);

                if (clientProperty != null)
                {
                    if (!string.IsNullOrEmpty(clientProperty.Name))
                        optName = clientProperty.Name;

                    if (_value == null)
                    {
                        if (clientProperty.DefaultValue != null)
                            _value = clientProperty.DefaultValue;
                        else
                        {
                            if (clientProperty.AllowNull)
                                AddOption(optName, "null", false);
                            continue;
                        }
                    }

                    // if (option.PropertyType==typeof(int))

                    switch (clientProperty.ValueType)
                    {
                        case JavaScriptTypes.Boolean:
                            AddOption(optName, (bool)_value);
                            break;
                        case JavaScriptTypes.JSON:
                            AddJSON(optName, _value);
                            break;
                        case JavaScriptTypes.Function:
                            AddFunctionOption(optName, _value.ToString(), clientProperty.FunctionParams);
                            break;
                        case JavaScriptTypes.Object:
                            OptionBuilder sub = new OptionBuilder();
                            sub.AddOptions(_value);
                            AddOption(optName, sub.ToString(), false);
                            break;
                        case JavaScriptTypes.jQuerySelector:
                            AddOption(optName, _value.ToString(), false);
                            break;
                        default:
                            AddOption(optName, _value);
                            break;
                    }

                }
                else
                {
                    if (_value == null)
                        continue;
                    AddOption(optName, _value);
                }
            }
            return this;
        }

        #region AddOption

        /// <summary>
        ///  Add object instance to option.
        /// </summary>
        /// <param name="name">Specified the option name.</param>
        /// <param name="value">Specified the object value.</param>
        /// <returns>The current OptionBuilder instance.</returns>
        public OptionBuilder AddOption(string name, object value)
        {
            if (value == null)
                return this;

            Type t = value.GetType();
            if (t == typeof(string))
            {
                AddOption(name, value.ToString(), true);
                return this;
            }

            if (t == typeof(string[]))
            {
                AddOption(name, (string[])value);
                return this;
            }

            if (t == typeof(int))
            {
                AddOption(name, (int)value);
                return this;
            }

            if (t == typeof(int[]))
            {
                AddOption(name, (int[])value);
                return this;
            }

            if (t == typeof(float))
            {
                AddOption(name, (float)value);
                return this;
            }

            if (t == typeof(float[]))
            {
                AddOption(name, (float[])value);
                return this;
            }

            if (t == typeof(double))
            {
                AddOption(name, (double)value);
                return this;
            }

            if (t == typeof(decimal))
            {
                AddOption(name, (decimal)value);
                return this;
            }

            if (t == typeof(bool))
            {
                AddOption(name, (bool)value);
                return this;
            }

            if (t.IsEnum)
            {
                AddOption(name, (Enum)value);
                return this;
            }
            if (t.IsGenericType)
            {
                AddJSON(name, value);
                return this;
            }
            return this;
        }

        /// <summary>
        ///  Add the function object to option.
        /// </summary>
        /// <param name="name">Specified the option name.</param>
        /// <param name="script">Specified the function body.</param>
        /// <returns>The current OptionBuilder instance.</returns>
        public OptionBuilder AddFunctionOption(string name, string script)
        {
            return AddFunctionOption(name, script, null);
        }

        /// <summary>
        /// Add the function object to option with parameters.
        /// </summary>
        /// <param name="name">Specified the option name.</param>
        /// <param name="script">Specified the function body.</param>
        /// <param name="functionParams">The function parameters.</param>
        /// <returns>The current OptionBuilder instance.</returns>
        public OptionBuilder AddFunctionOption(string name, string script, string[] functionParams)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("function");

            sb.Append("(");
            if (functionParams != null)
            {
                if (functionParams.Length > 0)
                    sb.Append(string.Join(",", functionParams));
            }
            sb.Append(")");
            sb.Append("{" + script + "}");
            return AddOption(name, sb.ToString());
        }

        public OptionBuilder AddFunctionOption(string name, Action<StringBuilder> value)
        {
            return AddFunctionOption(name, null, value);
        }

        public OptionBuilder AddFunctionOption(string name, string[] functionParams, Action<StringBuilder> value)
        {
            var scriptBuilder = new StringBuilder();
            value.Invoke(scriptBuilder);
            return AddFunctionOption(name, scriptBuilder.ToString(), functionParams);
        }

        /// <summary>
        ///  Add the json object instance to options.
        /// </summary>
        /// <param name="name">Specified the option name.</param>
        /// <param name="value">The json object.</param>
        /// <returns>The current OptionBuilder instance.</returns>
        public OptionBuilder AddJSON(string name, object value)
        {
            if (value.GetType() == typeof(string))
                return AddOption(name, value.ToString(), false);

            if (value.GetType() == typeof(OptionBuilder))
                return AddOption(name, ((OptionBuilder)value).ToString(), false);

            return AddOption(name, (new JavaScriptSerializer()).Serialize(value), false);
        }

        /// <summary>
        /// Append the name and string value to option string
        /// </summary>
        /// <remarks>
        /// the value will be convert to 'value' format
        /// </remarks>
        /// <param name="name">Specified the option name</param>
        /// <param name="value">option value of string</param>
        /// <returns>The current OptionBuilder instance.</returns>
        public OptionBuilder AddOption(string name, string value)
        {
            if (opts.ContainsKey(name))
                opts[name] = value;
            else
                opts.Add(name, value);
            return this;
        }

        /// <summary>
        ///  Add the string value to the options.
        /// </summary>
        /// <param name="name">Specified the option name</param>
        /// <param name="value">The string value to add to option.</param>
        /// <param name="format">Specified the wheather add the string in "'"</param>
        /// <returns>The current OptionBuilder instance.</returns>
        public OptionBuilder AddOption(string name, string value, bool format)
        {
            if (format)
                AddOption(name, "'" + value + "'");
            //AddOption(name,"\""+value+"\"");
            else
                AddOption(name, value);
            return this;
        }

        /// <summary>
        ///  Append the name and int value to option string
        /// </summary>
        /// <param name="name">option name</param>
        /// <param name="value">option value of integer</param>
        public OptionBuilder AddOption(string name, int value)
        {
            opts.Add(name, value.ToString());
            return this;
        }

        /// <summary>
        ///  Append the name and float value to option string
        /// </summary>
        /// <param name="name">option name</param>
        /// <param name="value">option value of float</param>
        public OptionBuilder AddOption(string name, float value)
        {
            opts.Add(name, value.ToString());
            return this;
        }

        /// <summary>
        ///  Append the name and double value to option string
        /// </summary>
        /// <param name="name">option name</param>
        /// <param name="value">option value of double</param>
        public OptionBuilder AddOption(string name, double value)
        {
            opts.Add(name, value.ToString());
            return this;
        }
        /// <summary>
        ///  Append the name and decimal value to option string
        /// </summary>
        /// <param name="name">option name</param>
        /// <param name="value">option value of decimal</param>
        public OptionBuilder AddOption(string name, decimal value)
        {
            opts.Add(name, value.ToString());
            return this;
        }

        /// <summary>
        ///  Append the name and bool value to option string
        /// </summary>
        /// <remarks>
        ///  the value will be convert to low case string.
        /// </remarks>
        /// <param name="name">option name</param>
        /// <param name="value">option value of bool</param>
        public OptionBuilder AddOption(string name, bool value)
        {
            opts.Add(name, value.ToString().ToLower());
            return this;
        }

        /// <summary>
        ///  Append the name and string array value to option string
        /// </summary>
        /// <param name="name">option name</param>
        /// <param name="value">option value of string array</param>
        public void AddOption(string name, string[] value)
        {
            string[] formatted = new string[value.Length];
            for (int i = 0; i < value.Length; i++)
            {
                if (value[i].StartsWith("'"))
                    formatted[i] = value[i];
                else
                    formatted[i] = "'" + value[i] + "'";
            }
            opts.Add(name, "[" + string.Join(",", formatted) + "]");
        }

        /// <summary>
        ///  Append the name and int array value to option string
        /// </summary>
        /// <param name="name">option name</param>
        /// <param name="value">option value of int array</param>
        public OptionBuilder AddOption(string name, int[] value)
        {
            string[] strValues = new string[value.Length];
            for (int i = 0; i < value.Length; i++)
                strValues[i] = value[i].ToString();
            opts.Add(name, "[" + string.Join(",", strValues) + "]");
            return this;
        }

        /// <summary>
        ///  Append the name and float array value to option string
        /// </summary>
        /// <param name="name">option name</param>
        /// <param name="value">option value of float array</param>
        public OptionBuilder AddOption(string name, float[] value)
        {
            string[] strValues = new string[value.Length];
            for (int i = 0; i < value.Length; i++)
                strValues[i] = value[i].ToString();
            opts.Add(name, "[" + string.Join(",", strValues) + "]");
            return this;
        }

        /// <summary>
        /// Append the name and Enum object value to option string
        /// </summary>
        /// <remarks>
        /// the Enum object will be convert to low case string.
        /// </remarks>
        /// <param name="name">option name</param>
        /// <param name="value">option value of Enum object</param>
        /// <returns>The current OptionBuilder instance.</returns>
        public OptionBuilder AddOption(string name, Enum value)
        {
            opts.Add(name, "'" + value.ToString().ToLower() + "'");
            return this;
        }

        /// <summary>
        ///  Add the Unit type value to the option.
        /// </summary>
        /// <param name="name">Specified the option name.</param>
        /// <param name="value"></param>
        /// <param name="isStringValue"></param>
        ///  <returns>The current OptionBuilder instance.</returns>
        public OptionBuilder AddOption(string name, Unit value, bool isStringValue)
        {
            if (isStringValue)
                AddOption(name, value.ToString(), true);
            else
                AddOption(name, value.Value);
            return this;
        }

        #endregion

        /// <summary>
        ///  Receives the option string .
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            if (opts.Count > 0)
            {
                builder.Append("{");
                int i = 0;
                foreach (string key in opts.Keys)
                {
                    if (i > 0)
                        builder.Append(",");
                    builder.Append(key + ":" + opts[key]);
                    i++;
                }
                builder.Append("}");
            }
            return builder.ToString();
        }
    }
}
